/**
* 只能补仓策略
 */
package order

import (
	"encoding/json"
	"fmt"
	"math"
	"time"
	"trade/db"
	"trade/huobiapi/client"
	"trade/huobiapi/email"
	"trade/logger"
	"trade/model"
)

type CoverPriceHistory struct {
	LastCoverPrice float64
	LastCoverTime  int64
	LastCoverRate  float64
	OrderStart     int64
	HasSendEmail   bool
	FirstIndex     float64 //第一次补仓的指数信息
	Times          int     //第一次补仓的指数信息
}

func (this *CoverPriceHistory) Load(db *db.DB) {
	lastCoverBytes, err := db.DBWriter.Get([]byte("cover_history"), nil)
	if err == nil {
		_ = json.Unmarshal(lastCoverBytes, this)
	}
}

func (this *CoverPriceHistory) Save(db *db.DB) {
	b, err := json.Marshal(*this)
	if err != nil {
		logger.Log.Debug(err)
		return
	}
	err = db.DBWriter.Put([]byte("cover_history"), b, nil)
	if err != nil {
		logger.Log.Debug(err)
	}
	return
}

func (this *CoverPriceHistory) Reset(db *db.DB) {
	this.LastCoverRate = 0
	this.LastCoverPrice = 0
	this.LastCoverTime = 0
	this.OrderStart = 0
	this.Times = 0
	this.HasSendEmail = false
	this.Save(db)
	return
}

func (this *HuobiOrder) CoverOrder(start int64, holder model.Position, lastCover *CoverPriceHistory) (coverOrder bool) {
	coverOrder = false
	currentTime := time.Now().Unix()
	accountInfo, err := this.Client.AccountInfo(this.conf.TradeConf.Symbol)
	if err != nil {
		logger.Log.Error(err)
		return
	}
	//15 分钟线
	klineBytes, err := this.Market.RequestKline(fmt.Sprintf("market.%s.kline.%s", this.conf.TradeConf.Contract, "15min"), this.ReqKlineID, currentTime-15*60, currentTime)
	if err != nil {
		logger.Log.Error(err)
		return
	}
	b, _ := json.Marshal(klineBytes)
	var kli model.Kline
	_ = json.Unmarshal(b, &kli)
	if len(kli.Data) < 1 {
		return
	}
	if accountInfo.RiskRate*100 <= 100 || accountInfo.MarginBalance+accountInfo.ProfitUnreal < 10 {
		//如果保证率只剩下100% 则不能再继续 基本爆仓
		logger.Log.Warning("当前保证金不足了，账户余额也只剩下不到10个eth，请谨慎操作～")
		return
	}
	if time.Now().Unix()-start < this.conf.TradeConf.CoverTime { //15 分钟进行一次补仓位
		logger.Log.Debugf("15分钟前刚补过仓位～补仓价：%f,补仓时亏损：%.3f", lastCover.LastCoverPrice, lastCover.LastCoverRate)
		return
	}
	targetBucangRate := -0.2
	if lastCover.LastCoverRate < 0 {
		targetBucangRate = 2 * lastCover.LastCoverRate
	}
	targetPrice := holder.CostHold
	if holder.Direction == "sell" {
		targetPrice += holder.CostHold * math.Abs(targetBucangRate) / float64(this.conf.TradeConf.LeverRate)
	}
	if holder.Direction == "buy" {
		targetPrice -= holder.CostHold * math.Abs(targetBucangRate) / float64(this.conf.TradeConf.LeverRate)
	}
	if holder.ProfitRate < targetBucangRate { //如果 收益率在 -20% 以上 进行一次 按比例补仓  每亏 20% 补一次仓位 这种方式很保险
		logger.Log.Debugf("上次补仓在 亏损 %.3f 位置下单", lastCover.LastCoverRate)
		volume := math.Abs(holder.ProfitRate) * float64(this.conf.TradeConf.Volume)
		volumeInt64 := int64(volume) * 10
		//如果 止损 范围外 那么 考虑补仓
		//判断最新价与15分钟最低价 最高价比较
		if holder.Direction == "buy" {
			//如果是补仓多单 判断 最后价格是否与 15分钟最低价接近
			logger.Log.Debugf("多单补仓参考信息：15分钟最低价:$%.3f 最后成交价:$%.3f 待补仓数量：%d", kli.Data[0].Low, holder.LastPrice, volumeInt64)
			minPrice := kli.Data[0].Low
			if minPrice > holder.LastPrice {
				minPrice = holder.LastPrice
			}
			if lastCover.LastCoverPrice > 0 && lastCover.LastCoverPrice < minPrice {
				logger.Log.Error("多单补仓单比上次价格还高 不合理 上次补仓价：", lastCover.LastCoverPrice)
				return
			}
			if lastCover.Times >= 4 {
				logger.Log.Error("当前已经补仓4次，非常危险哦！：", lastCover.Times, "账户权益：", accountInfo.MarginBalance, "未实现盈亏：", accountInfo.ProfitUnreal, "爆仓价：", accountInfo.LiquidationPrice)
				go this.BaoCangEmail(holder.Direction, fmt.Sprintf("账户权益:%.3f,未实现盈亏:%.3f,爆仓价:%.3f", accountInfo.MarginBalance, accountInfo.ProfitUnreal, accountInfo.LiquidationPrice))
				return
			}
			//补仓多单
			//下单操作
			ores, err := this.Client.Order(client.Order{
				Symbol:         this.conf.TradeConf.Symbol,
				ContractType:   this.conf.TradeConf.ContractType,
				ContractCode:   this.conf.TradeConf.ContractCode,
				ClientOrderId:  time.Now().Unix(),
				Price:          minPrice,
				Volume:         volumeInt64,
				Direction:      holder.Direction,
				Offset:         "open",
				LeverRate:      this.conf.TradeConf.LeverRate,
				OrderPriceType: "limit",
			})
			if err != nil {
				logger.Log.Error("补仓失败:", err)
				return
			}
			if ores.Status == "ok" {
				return this.ListeningCoverOrder(client.QueryOrder{
					OrderID: fmt.Sprintf("%d", ores.Data.OrderID),
					Symbol:  this.conf.TradeConf.Symbol,
				}, 60, lastCover, holder.ProfitRate)
			}
			return
		} else {
			logger.Log.Debugf("空单补仓参考信息：15分钟最高价:$%.3f 最后成交价:$%.3f", kli.Data[0].High, holder.LastPrice)
			// 如果是补仓空单 判断 最后价格是否与 15分钟最高价接近
			maxPrice := kli.Data[0].High
			if maxPrice > holder.LastPrice {
				maxPrice = holder.LastPrice
			}
			if lastCover.LastCoverPrice > 0 && lastCover.LastCoverPrice > maxPrice {
				logger.Log.Error("空单补仓单比上次价格还低 不合理 上次补仓价：", lastCover.LastCoverPrice)
				return
			}
			// 补仓空单
			if lastCover.Times >= 4 {
				logger.Log.Error("当前已经补仓4次，非常危险哦！：", lastCover.Times, "账户权益：", accountInfo.MarginBalance, "未实现盈亏：", accountInfo.ProfitUnreal, "爆仓价：", accountInfo.LiquidationPrice)
				go this.BaoCangEmail(holder.Direction, fmt.Sprintf("账户权益:%.3f,未实现盈亏:%.3f,爆仓价:%.3f", accountInfo.MarginBalance, accountInfo.ProfitUnreal, accountInfo.LiquidationPrice))
				return
			}
			// 下单操作
			ores, err := this.Client.Order(client.Order{
				Symbol:         this.conf.TradeConf.Symbol,
				ContractType:   this.conf.TradeConf.ContractType,
				ContractCode:   this.conf.TradeConf.ContractCode,
				ClientOrderId:  time.Now().Unix(),
				Price:          maxPrice,
				Volume:         volumeInt64,
				Direction:      holder.Direction,
				Offset:         "open",
				LeverRate:      this.conf.TradeConf.LeverRate,
				OrderPriceType: "limit",
			})
			if err != nil {
				logger.Log.Error("补仓失败:", err)
				return
			}
			if ores.Status == "ok" {
				return this.ListeningCoverOrder(client.QueryOrder{
					OrderID: fmt.Sprintf("%d", ores.Data.OrderID),
					Symbol:  this.conf.TradeConf.Symbol,
				}, 60, lastCover, holder.ProfitRate)
			}
			return
		}
	}
	logger.Log.Debugf("上次补仓在 亏损 %.3f 位置下单 预计下次补仓位在亏损位：%.3f 预计补仓价格：$%.3f", math.Abs(lastCover.LastCoverRate), math.Abs(targetBucangRate), targetPrice)
	return false
}

func (this *HuobiOrder) ListeningCoverOrder(o client.QueryOrder, timeout int64, lastCover *CoverPriceHistory, rate float64) bool {
	start := time.Now().Unix()
	for {
		detail, err := this.Client.OrderDetail(o)
		if err != nil {
			logger.Log.Error("获取详情出错~1秒后重试", err)
			time.Sleep(1 * time.Second)
			continue
		}
		if detail.Status == client.STATUS_ALL_SUCCESS {
			logger.Log.Infof("补仓成功 补仓订单：%d 补仓价：$%.3f", detail.OrderID, detail.Price)
			lastCover.LastCoverPrice = detail.Price
			lastCover.LastCoverTime = time.Now().Unix()
			lastCover.LastCoverRate = rate
			lastCover.Times++
			lastCover.Save(this.db)
			return true
		}
		//超时未成交则撤单
		if time.Now().Unix()-start > timeout {
			if this.Client.CancelOrder(o) {
				break
			}
		}
		time.Sleep(1 * time.Second)
	}
	return false
}

func (this *HuobiOrder) BaoCangEmail(direction string, body string) {
	mailTo := []string{
		this.conf.TradeConf.MailTo,
	}
	subject := direction + "!!!合约爆仓预警提醒，非常重要，请查看仓位!!!"
	// 邮件正文
	_ = email.SendMail(this.conf, mailTo, subject, body)
}
